/*
----------------------------------------------------------------
This is a mini Ogre Application. It does nothing.
by Mythma (mythma@126.com)
--------------------------
Eihort porting: stoneCold
----------------------------------------------------------------
*/

#define OIS_DYNAMIC_LIB

#include "Ogre.h"
#include "OgreFrameListener.h"
#include <OIS/OIS.h>

using namespace Ogre;

const String NameCamera = "field_camera";
const String NameScene = "field_scene";
const String NameNodeHero = "robot_node";

//////////////////////////////////////////////////////////////////////////
// class SimpleFrameListener
//////////////////////////////////////////////////////////////////////////
class SimpleFrameListener : public FrameListener
{
public:

	SimpleFrameListener(OIS::Keyboard* keyboard, OIS::Mouse* mouse, SceneManager* _sceneMgr )
	{
	    sceneMgr = _sceneMgr;
		mKeyboard = keyboard;
		mMouse = mouse;
		stop_flag = false;
		camera = sceneMgr->getCamera(NameCamera);
	}

        // этот метод вызывается перед началом отрисовки следующего кадра
	bool frameStarted(const FrameEvent& evt)
	{
            // обносить состояние устройств ввода
		if(mKeyboard) mKeyboard->capture();
		if(mMouse) mMouse->capture();

            // выход по KC_ESCAPE или левой кнопке мыши
		if( (mKeyboard && mKeyboard->isKeyDown(OIS::KC_ESCAPE)) ||
		    (mMouse && mMouse->getMouseState().buttonDown(OIS::MB_Right)) )
		{
			return false;
		}
        else if( mKeyboard )
        {
            if( mKeyboard->isKeyDown(OIS::KC_SUBTRACT) ) // минус на цифроклавиатуре
                CameraMove(0.2);
            else if( mKeyboard->isKeyDown(OIS::KC_ADD) ) // плюс на цифроклавиатуре
                CameraMove(-0.1);
            else if( mKeyboard->isKeyDown(OIS::KC_NUMPAD4) ) // 4 на цифроклавиатуре
                CameraRotateYaw(0.5);
            else if( mKeyboard->isKeyDown(OIS::KC_NUMPAD6) ) // 6 на цифроклавиатуре
                CameraRotateYaw(-0.5);
            else if( mKeyboard->isKeyDown(OIS::KC_NUMPAD8) ) // 8 на цифроклавиатуре
                CameraRotatePitch(0.5);
            else if( mKeyboard->isKeyDown(OIS::KC_NUMPAD2) ) // 2 на цифроклавиатуре
                CameraRotatePitch(-0.5);
            else if( mKeyboard->isKeyDown(OIS::KC_NUMPAD7) ) // 7 на цифроклавиатуре
                CameraRotateRoll(0.5);
            else if( mKeyboard->isKeyDown(OIS::KC_NUMPAD9) ) // 9 на цифроклавиатуре
                CameraRotateRoll(-0.5);
        }

            // остановить/запустить вращение
        if( mMouse && mMouse->getMouseState().buttonDown(OIS::MB_Left) )
	    {
	        //const OIS::MouseState &ms = mMouse->getMouseState();
            stop_flag = !stop_flag;
	    }

		return true;
	}

	bool frameRenderingQueued(const FrameEvent& evt)
	{
	    const OIS::MouseState &ms = mMouse->getMouseState();
	    if (ms.buttonDown(OIS::MB_Left))
        {
                // подвигать камерой над плоскостью
                // (наверное, надо сделать проверку на выход  границу видимого изображения)
            camera->moveRelative(Vector3(ms.X.rel * -0.5, ms.Y.rel * 0.5, 0.0));
        }

        TranslateNode(evt);

		return FrameListener::frameRenderingQueued(evt);
    }

        // этот метод вызывается в конце отрисовки кадра
	bool frameEnded(const FrameEvent& evt)
	{
		return true;
	}

private:
    SceneManager* sceneMgr;
	OIS::Keyboard* mKeyboard;
	OIS::Mouse* mMouse;
	Camera* camera;
	bool stop_flag; // останавливать вращение, если этот флаг true

       // двигать объект, если не запрещено
    void TranslateNode(const FrameEvent& evt)
    {
	    if( stop_flag )
            return;
        //node1->yaw(Degree(evt.timeSinceLastFrame * 30));
        //node1->rotate( Vector3::UNIT_Y, Degree(-1) );

        Node* node1 = sceneMgr->getRootSceneNode()->getChild(NameNodeHero);
        if( node1 )
        {
            Vector3 pos = node1->getPosition();
            if( pos.x < 220 && (pos.z <= -220 || pos.z==0))
            {
                //Quaternion q = node1->getOrientation();
                //if( q.y == 0 )
                //    node1->yaw(Degree(0.5));
                node1->translate(Vector3(evt.timeSinceLastFrame * 100, 0, 0));
                //node1->setOrientation(q);
            }
            if( pos.x >= 220 && pos.z < 220 )
                node1->translate(Vector3(0, 0, evt.timeSinceLastFrame * 100));
            if( pos.x > -220 && pos.z >= 220 )
                node1->translate(Vector3(evt.timeSinceLastFrame * -100, 0, 0));
            if( pos.x <= -220 && pos.z > -220 )
                node1->translate(Vector3(0, 0, evt.timeSinceLastFrame * -100));
        }
/*
220,220                      -220,220


                    0,0


220,-220                     -220,-220
*/
    }
        // перемещение камеры по оси y ("сверху" - "вглубь")
    void CameraMove(Real step)
    {
        Vector3 vector_pos( camera->getPosition() );
        if( step != 0 && (vector_pos.y > 1 || step > 0) && (vector_pos.y < 10000 || step < 0) )
        {
            vector_pos.y = step;
            camera->move( vector_pos );
        }
    }

	        // вращение объекта по оси y ("сверху" - "вглубь")
    void CameraRotateYaw(Real degree)
    {
        Node* node1 = sceneMgr->getRootSceneNode()->getChild(NameNodeHero);
        if( node1 )
            node1->yaw(Degree(degree));
    }

	        // вращение объекта по оси y ("сверху" - "вглубь")
    void CameraRotateRoll(Real degree)
    {
        Node* node1 = sceneMgr->getRootSceneNode()->getChild(NameNodeHero);
        if( node1 )
            node1->roll(Degree(degree));
    }

	        // вращение объекта по оси y ("сверху" - "вглубь")
    void CameraRotatePitch(Real degree)
    {
        Node* node1 = sceneMgr->getRootSceneNode()->getChild(NameNodeHero);
        if( node1 )
            node1->pitch(Degree(degree));
    }
};

//////////////////////////////////////////////////////////////////////////
// class SimpleKeyListener
//////////////////////////////////////////////////////////////////////////
class SimpleKeyListener : public OIS::KeyListener
{
public:
	bool keyPressed(const OIS::KeyEvent& e){ return true; }

	bool keyReleased(const OIS::KeyEvent& e){ return true; }
};

//////////////////////////////////////////////////////////////////////////
// class SimpleMouseListener
//////////////////////////////////////////////////////////////////////////
class SimpleMouseListener : public OIS::MouseListener
{
public:
	bool mouseMoved(const OIS::MouseEvent& e){ return true; }

	bool mousePressed(const OIS::MouseEvent& e, OIS::MouseButtonID id){ return true; }

	bool mouseReleased(const OIS::MouseEvent& e, OIS::MouseButtonID id){ return true; }
};

SceneNode* create_one_hero(SceneManager* sceneMgr)
{
    Entity *ent1 = sceneMgr->createEntity( "Robot", "Mesh.001.mesh" );
    SceneNode *node1 = sceneMgr->getRootSceneNode()->createChildSceneNode( NameNodeHero );

    node1->attachObject( ent1 );
    node1->scale(20, 20, 20); // масштаб
	//node1->pitch(Degree(180));
	//node1->roll(Degree(180));
	node1->translate(Vector3(0,40,0)); // немного поднять над полем

    return node1;
}

void create_textured_plane(SceneManager* sceneMgr)
{
    Plane OceanPlane;

        // cоздание нормали - ось перпендикулярная плоскости
	OceanPlane.normal = Vector3::UNIT_Y;

        // создание плоскости с именем "OceanPlane"
	MeshManager::getSingleton().createPlane(
		"OceanPlane",
		ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
		OceanPlane,
		500, 500, // размер width-height
		2, 2, // x-y segments
		true, 1,
		10, 10, // на сколько квадратов будет разбита плоскость
		Vector3::UNIT_Z);

        // создание узла, cодержащего плоскость "OceanPlane"
	Entity* OceanEntity = sceneMgr->createEntity("water", "OceanPlane");
        // вот это собственно и есть установка текстуры
        // (c именем Examples/10PointBlock из файла Examples.material)
	OceanEntity->setMaterialName("Examples/10PointBlock");

        // добавление плоскости в сцену, как обычно
	SceneNode* OceanNode = sceneMgr->getRootSceneNode()->createChildSceneNode("OceanNode");
	OceanNode->attachObject(OceanEntity);
	//OceanNode->translate(150000, 5500, 150000);
}

void create_2d_background(SceneManager* sceneMgr)
{
        //  материала для фона
    MaterialPtr material = MaterialManager::getSingleton().create("Background_Testtest", "General");
    material->getTechnique(0)->getPass(0)->createTextureUnitState("rockwall.tga");
    material->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); // без этого робота не видно
    //material->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false);
    //material->getTechnique(0)->getPass(0)->setLightingEnabled(false);

        // создание прямоугольника, который будет закрывать  экран
    Rectangle2D* rect = new Rectangle2D(true);
    rect->setCorners(-1.0, 1.0, 1.0, -1.0); // левый-верхний-правый-нижний, здесь максимальные значения
    rect->setMaterial("Background_Testtest");

        // Render the background before everything else
    rect->setRenderQueueGroup(RENDER_QUEUE_BACKGROUND);

    // Hacky, but we need to set the bounding box to something big
    // NOTE: If you are using Eihort, please see the note below on setting the bounding box
    rect->setBoundingBox(AxisAlignedBox(-100.0*Vector3::UNIT_SCALE, 100.0*Vector3::UNIT_SCALE));

        // добавление прямоугольника в сцену, как обычно :)
    SceneNode* node = sceneMgr->getRootSceneNode()->createChildSceneNode("Background");
    node->attachObject(rect);

        // пример с автоматическим скролированием
    //material->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setScrollAnimation(-0.25, 0.0);
}

void create_fog(SceneManager* sceneMgr, SceneNode* node1)
{
     		// тут у нас туманчик - он же выхлопной газ
    ParticleSystem* pSys2 =sceneMgr->createParticleSystem("fountain1", "Examples/Smoke");
    node1->attachObject(pSys2);
    node1->rotate( Vector3::UNIT_X, Degree(-90) );
}

//////////////////////////////////////////////////////////////////////////
// main
//////////////////////////////////////////////////////////////////////////
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
	#define WIN32_LEAN_AND_MEAN
	#include "windows.h"
	INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
	int main(int argc, char **argv)
#endif
{
	//-----------------------------------------------------
	// 1 enter ogre
	//-----------------------------------------------------
	Root* root = new Root;

	//-----------------------------------------------------
	// 2 configure resource paths
	//-----------------------------------------------------
	// Load resource paths from config file

	// File format is:
	//  [ResourceGroupName]
	//  ArchiveType=Path
	//  .. repeat
	// For example:
	//  [General]
	//  FileSystem=media/
	//  Zip=packages/level1.zip

	ConfigFile cf;
	cf.load("resources.cfg");

	// Go through all sections & settings in the file
	ConfigFile::SectionIterator seci = cf.getSectionIterator();

	String secName, typeName, archName;
	while (seci.hasMoreElements())
	{
		secName = seci.peekNextKey();
		ConfigFile::SettingsMultiMap *settings = seci.getNext();
		ConfigFile::SettingsMultiMap::iterator i;
		for (i = settings->begin(); i != settings->end(); ++i)
		{
			typeName = i->first;
			archName = i->second;
			ResourceGroupManager::getSingleton().addResourceLocation(
				archName, typeName, secName);
		}
	}
	//-----------------------------------------------------
	// 3 настроить приложение и создать окно
	//-----------------------------------------------------
	if(!root->showConfigDialog())
	{
		delete root;
		return false; // Exit the application on cancel
	}

	RenderWindow* window = root->initialise(true, "Simple Ogre App");

	ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

	//-----------------------------------------------------
	// 4 cоздать менеджер сцены SceneManager
	//
	//		ST_GENERIC = octree
	//		ST_EXTERIOR_CLOSE = simple terrain
	//		ST_EXTERIOR_FAR = nature terrain (depreciated)
	//		ST_EXTERIOR_REAL_FAR = paging landscape
	//		ST_INTERIOR = Quake3 BSP
	//-----------------------------------------------------
	SceneManager* sceneMgr = root->createSceneManager( ST_GENERIC, NameScene );

	//-----------------------------------------------------
	// 5 cоздать камeру
	//-----------------------------------------------------
	Camera* camera = sceneMgr->createCamera(NameCamera);
    camera->setPosition( 0, 630, -300 ); // переместиться
    camera->lookAt( 0, 0, 1); // посмотреть
    camera->setNearClipDistance(0.1);

	//-----------------------------------------------------
	// 6 Create one viewport, entire window
	//-----------------------------------------------------
	Viewport* viewPort = window->addViewport(camera);
	viewPort->setBackgroundColour( ColourValue( 0, 0, 0 ) );

	//-----------------------------------------------------
	// помещаем источник света в сцену
	//-----------------------------------------------------
	Light* testLight; // помещение источника света в сцену
	sceneMgr->setAmbientLight( ColourValue(0.35, 0.35, 0.35) );
	sceneMgr->setShadowTechnique(SHADOWTYPE_STENCIL_ADDITIVE);
	testLight = sceneMgr->createLight("Sun");
	//testLight->setType(Light::LT_DIRECTIONAL); // для направленного источника света
	testLight->setType(Light::LT_POINT); // для позиционного источника света
	testLight->setDiffuseColour(1.0, 1.0, 1.0);
	testLight->setSpecularColour(1.0, 1.0, 1.0);
	//testLight->setDirection(Vector3(0, sin(m_SunY), sin(m_SunZ)));
	testLight->setPosition(1000, 1000, 1000); // поместил источник куда-нибудь

    create_one_hero(sceneMgr);
    create_textured_plane(sceneMgr);
    create_2d_background(sceneMgr);
    //create_fog(sceneMgr, node1); // туманчик!

	//----------------------------------------------------
	// 7 добавить перехват ввода через библиотеку OIS
	//----------------------------------------------------
	OIS::ParamList paramList;
	size_t windowHnd = 0;
	std::ostringstream windowHndStr;

	   // cообщить OIS oб окне огра
	window->getCustomAttribute("WINDOW", &windowHnd);
	windowHndStr << windowHnd;
	paramList.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));

	  // это надо сделать перед OIS::InputManager::createInputSystem( paramList )
	  // чтобы не полностью захватывал ввод с мыши и клавиатуры, иначе тяжело отлаживать
#if defined OIS_WIN32_PLATFORM
    paramList.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_FOREGROUND" )));
    paramList.insert(std::make_pair(std::string("w32_mouse"), std::string("DISCL_NONEXCLUSIVE")));
    paramList.insert(std::make_pair(std::string("w32_keyboard"), std::string("DISCL_FOREGROUND")));
    paramList.insert(std::make_pair(std::string("w32_keyboard"), std::string("DISCL_NONEXCLUSIVE")));
#elif defined OIS_LINUX_PLATFORM
    paramList.insert(std::make_pair(std::string("x11_mouse_grab"), std::string("false")));
    paramList.insert(std::make_pair(std::string("x11_mouse_hide"), std::string("false")));
    paramList.insert(std::make_pair(std::string("x11_keyboard_grab"), std::string("false")));
    paramList.insert(std::make_pair(std::string("XAutoRepeatOn"), std::string("true")));
#endif
	  // установить хендлеры для превата ввода клавиатуры и мыши
	OIS::InputManager* inputManager = OIS::InputManager::createInputSystem(paramList);
	OIS::Keyboard* keyboard = static_cast<OIS::Keyboard*>(inputManager->createInputObject(OIS::OISKeyboard, true));
	OIS::Mouse*	mouse = static_cast<OIS::Mouse*>(inputManager->createInputObject(OIS::OISMouse, true));

	   // cообщить библиотеке OIS о разрешении окна
	unsigned int width, height, depth;
	int top, left;
	window->getMetrics(width, height, depth, left, top);
	const OIS::MouseState& ms = mouse->getMouseState();
	ms.width = width;
	ms.height = height;

	   // улавливание ввода с клавиатуры
	SimpleKeyListener* keyListener = new SimpleKeyListener();
	keyboard->setEventCallback(keyListener);
	   // улавливание мышиных событий
	SimpleMouseListener* mouseListener = new SimpleMouseListener();
	mouse->setEventCallback(mouseListener);
	   // главный класс отрисовки сцены
	SimpleFrameListener* frameListener = new SimpleFrameListener(keyboard, mouse, sceneMgr );
	root->addFrameListener(frameListener);

	//----------------------------------------------------
	// 8 старт отрисовки
	//----------------------------------------------------
	root->startRendering();

	//----------------------------------------------------
	// 9 очистка всего
	//----------------------------------------------------
	   // удаление объектов OIS
	inputManager->destroyInputObject(mouse);
	mouse = NULL;
	inputManager->destroyInputObject(keyboard);
	keyboard = NULL;
	OIS::InputManager::destroyInputSystem(inputManager);
	inputManager = NULL;
	   // удаление обработчиков (listeners)
	delete frameListener;
	delete mouseListener;
	delete keyListener;
	   // удаление сцены Ogre
	delete root;

	return 0;
}
